package cn.zhxu.toys.concurrent;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;


/**
 * Redis并发同步锁
 * 
 * @author Troy
 * 
 * @since 0.1.6
 */
public class RedisSyncLock extends AbstractSyncLock {

	static final Logger log = LoggerFactory.getLogger(RedisSyncLock.class);

	private JedisPool jedisPool;

	/**
	 * 上锁
	 * @param key Redis Key
	 * @param requestId Redis Value
	 * @param maxHoldSeconds Redis Expire Time
	 * @return 是否上锁成功
	 */
	protected boolean doTryLock(String key, String requestId, int maxHoldSeconds) {
		// 获取Jedis连接放在循环内部
		try (Jedis jedis = jedisPool.getResource()) {
			/*
			  nxxx:
			  		NX: not exists, 只有key 不存在时才把key value set 到redis
			  		XX: is exists ，只有 key 存在是，才把key value set 到redis
			  expx:
			  		EX ： seconds 秒
		      		PX : milliseconds 毫秒
		      上锁与过期时间设置必须具有原子性，否则可能造成死锁（过期时间设置失败时）
			 */
			SetParams params = SetParams.setParams().ex(maxHoldSeconds).nx();

			String result = jedis.set(key, requestId, params);

			log.debug("GET_LOCK[" + key + "]: " + result);
			return result != null && result.toUpperCase().contains("OK");
		}
		// 及时释放连接，以免在高并发下造成连接耗尽问题
	}

	protected void doUnLock(String key, String requestId) {
		try (Jedis jedis = jedisPool.getResource()) {
			/*
			  判断值与删除键必须具有原子性，否则可能误解别人新加的锁（当该锁正好要过期时）
			 */
			log.debug("DEL_LOCK: " + key);

			String script = "if redis.call('get', KEYS[1]) == ARGV[1] then redis.call('del', KEYS[1]) end";
			jedis.eval(script, Collections.singletonList(key), Collections.singletonList(requestId));

//		    jedis.watch(key);
//		    Transaction transaction = jedis.multi();
//		    String value = jedis.get(key);
//		    if (requestId != null && requestId.equals(value)) {
//		      	jedis.del(key);
//		    }
//		    transaction.exec();
//		    jedis.unwatch();
		}
	}

	public JedisPool getJedisPool() {
		return jedisPool;
	}

	public void setJedisPool(JedisPool jedisPool) {
		this.jedisPool = jedisPool;
	}

}
